# backend/modules/userinterface/routes.py

import os
from flask_restful import Resource
from flask import request, send_file, current_app
from ...extensions import db
from .models import Report, DemandForecast, InventoryManagement
from flask_jwt_extended import jwt_required, get_jwt_identity
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import datetime
import matplotlib.pyplot as plt
import uuid
import json
import pytz
from sqlalchemy.orm import joinedload
from ...modules.dataimport.models import UploadedFile
from ...modules.forecasting.models import Forecast
from matplotlib.font_manager import FontProperties
from collections import defaultdict
from ...modules.authentication.models import User


class UserInterfaceRoutes:
    def __init__(self, api):
        # 添加用户接口的路由
        api.add_resource(GetReports, '/api/userinterface/report')  # 获取报告
        api.add_resource(GenerateReport, '/api/userinterface/report/generate')  # 生成报告
        api.add_resource(DownloadReport, '/api/userinterface/report/download/<int:report_id>')  # 下载报告
        api.add_resource(DeleteReport, '/api/userinterface/report/delete/<int:report_id>')  # 删除报告
        api.add_resource(GetForecastDataByDate, '/api/forecast/data_by_date')  # 根据日期获取预测数据


class GetReports(Resource):
    @jwt_required()  # JWT 认证
    def get(self):
        # 获取当前用户
        current_user = get_current_user()
        if not current_user or current_user.role not in ['管理员', '普通用户']:
            # 若用户未授权，则返回403状态
            return {"message": "未授权访问"}, 403

        try:
            if current_user.role == '管理员':
                # 管理员可以查看所有报告
                reports = Report.query.options(joinedload(Report.uploaded_file)).order_by(Report.timestamp.desc()).all()
            else:
                # 普通用户只能查看自己的报告
                user_file_ids = [file.id for file in current_user.uploaded_files]
                reports = Report.query.filter(Report.uploaded_file_id.in_(user_file_ids)).order_by(Report.timestamp.desc()).all()

            # 构建报告列表
            report_list = [
                {
                    "id": report.id,
                    "report_type": report.report_type,
                    "filepath": report.filepath,
                    "timestamp": report.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
                    "file_id": report.uploaded_file_id,
                    "file_name": report.uploaded_file.file_name if report.uploaded_file else "文件已删除或不存在"
                }
                for report in reports
            ]
            return {"reports": report_list}, 200  # 返回报告列表
        except Exception as e:
            # 处理异常并记录错误日志
            current_app.logger.error(f"获取报告失败: {e}")
            return {"message": "获取报告失败，请重试。"}, 500  # 返回500状态


class GenerateReport(Resource):
    @jwt_required()  # JWT 认证
    def post(self):
        # 获取当前用户
        current_user = get_current_user()
        if not current_user or current_user.role not in ['管理员', '普通用户']:
            # 若用户未授权，则返回403状态
            return {"message": "未授权访问"}, 403

        data = request.get_json()  # 获取请求的 JSON 数据
        forecast_id = data.get('forecast_id')  # 接收 forecast_id

        if not forecast_id:
            return {"message": "预测ID缺失"}, 400  # 返回400状态

        try:
            # 获取预测记录
            forecast = Forecast.query.get(forecast_id)
            if not forecast:
                return {"message": "预测记录不存在"}, 404  # 返回404状态

            # 权限控制
            if current_user.role != '管理员' and forecast.uploaded_file.user_id != current_user.id:
                return {"message": "未授权生成其他用户的报告"}, 403  # 返回403状态

            # 获取关联的上传文件
            uploaded_file = UploadedFile.query.get(forecast.uploaded_file_id)
            if not uploaded_file:
                return {"message": "关联的上传文件不存在"}, 404  # 返回404状态

            # 获取相关需求预测记录
            demand_forecasts = DemandForecast.query.filter_by(forecast_id=forecast_id).all()
            if not demand_forecasts:
                return {"message": "没有相关的需求预测记录"}, 400  # 返回400状态

            # 获取相关库存管理记录
            inventory_records = InventoryManagement.query.filter_by(uploaded_file_id=uploaded_file.id).all()
            if not inventory_records:
                return {"message": "没有相关的库存管理记录"}, 400  # 返回400状态

            # 生成报告文件名和路径
            timestamp_str = datetime.datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y%m%d%H%M%S')
            filename = f"report_{forecast_id}_{timestamp_str}.pdf"
            report_dir = os.path.join(current_app.root_path, 'reports')
            os.makedirs(report_dir, exist_ok=True)  # 创建报告目录
            filepath = os.path.join(report_dir, filename)

            # 生成报告内容
            self.generate_full_report(filepath, forecast, demand_forecasts, uploaded_file)

            # 保存报告记录到数据库，确保传递 forecast_id
            new_report = Report(
                report_type='FullReport',
                filepath=filepath,
                uploaded_file_id=uploaded_file.id,
                forecast_id=forecast.id,  # 确保 forecast_id 被设置
                timestamp=datetime.datetime.now(pytz.timezone('Asia/Shanghai'))  # 当前时间戳
            )
            db.session.add(new_report)  # 将报告记录添加到数据库
            db.session.commit()  # 提交数据库修改

            return {"message": "报告生成成功", "report_id": new_report.id}, 200  # 返回成功状态
        except Exception as e:
            db.session.rollback()  # 回滚数据库修改
            current_app.logger.error(f"报告生成失败: {e}", exc_info=True)  # 记录错误日志
            return {"message": f"报告生成失败: {str(e)}"}, 500  # 返回500状态

    def generate_full_report(self, filepath, forecast, demand_forecasts, uploaded_file):
        try:
            # 注册中文字体给 ReportLab
            font_dir = os.path.join(current_app.root_path, 'static', 'fonts')
            font_path = os.path.join(font_dir, 'msyh.ttf')  # 微软雅黑字体
            if not os.path.exists(font_path):
                current_app.logger.error(f"字体文件不存在: {font_path}")
                raise FileNotFoundError("字体文件缺失")  # 抛出文件未找到异常
            pdfmetrics.registerFont(TTFont("MSYH", font_path))  # 注册字体

            # 创建 FontProperties 对象
            font_prop = FontProperties(fname=font_path)

            # 配置 Matplotlib 使用中文字体
            plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

            styles = getSampleStyleSheet()
            chinese_style = ParagraphStyle(
                name='Chinese',
                parent=styles['Normal'],
                fontName='MSYH',
                fontSize=12,
                leading=15
            )

            # 创建文档模板
            doc = SimpleDocTemplate(filepath, pagesize=A4,
                                    rightMargin=72, leftMargin=72,
                                    topMargin=72, bottomMargin=18,
                                    embedFonts=True)

            # 内容列表
            elements = []

            # 报告标题
            elements.append(Paragraph("物流需求预测与库存管理报告", chinese_style))
            elements.append(Paragraph(
                f"生成时间: {datetime.datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S')}",
                chinese_style))
            elements.append(Spacer(1, 12))  # 添加间隔

            # 显示关联的文件和品类信息
            elements.append(Paragraph(f"关联文件: {uploaded_file.file_name}", chinese_style))
            elements.append(Paragraph(f"品类: {forecast.category}", chinese_style))
            elements.append(Spacer(1, 12))  # 添加间隔

            # 一、需求预测部分
            elements.append(Paragraph("一、需求预测", chinese_style))
            elements.append(Spacer(1, 12))  # 添加间隔

            # 需求预测表格
            data = [["日期", "预测需求量"]]
            for df in demand_forecasts:
                data.append([
                    df.forecast_date.strftime('%Y-%m-%d'),  # 格式化日期
                    df.forecast_quantity  # 预测需求量
                ])

            table = Table(data, repeatRows=1)  # 创建表格
            table.setStyle(TableStyle([
                ('FONTNAME', (0, 0), (-1, 0), 'MSYH'),  # 设置字体样式
                ('BACKGROUND', (0, 0), (-1, 0), colors.grey),  # 表头背景色
                ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),  # 表头字体颜色
                ('ALIGN', (0, 0), (-1, -1), 'CENTER'),  # 内容居中对齐
                ('FONTNAME', (0, 1), (-1, -1), 'MSYH'),  # 内容字体样式
                ('FONTSIZE', (0, 0), (-1, -1), 10),  # 设置字体大小
                ('BOTTOMPADDING', (0, 0), (-1, 0), 12),  # 表头底部边距
                ('BACKGROUND', (0, 1), (-1, -1), colors.beige),  # 内容背景色
                ('GRID', (0, 0), (-1, -1), 1, colors.black),  # 设置表格网格线
            ]))
            elements.append(table)  # 添加表格
            elements.append(Spacer(1, 12))  # 添加间隔

            # 生成需求趋势图
            chart_filename = f"demand_trend_{uuid.uuid4().hex}.png"  # 创建唯一的文件名
            chart_path = os.path.join(os.path.dirname(filepath), chart_filename)
            self.create_demand_trend_chart(demand_forecasts, chart_path, font_prop)  # 生成趋势图
            if os.path.exists(chart_path):
                im = Image(chart_path, width=6 * inch, height=4 * inch)
                elements.append(im)  # 添加图像到文档
                elements.append(Spacer(1, 12))  # 添加间隔

            # 二、库存管理建议部分
            elements.append(Paragraph("二、库存管理建议", chinese_style))
            elements.append(Spacer(1, 12))  # 添加间隔

            # 使用 Forecast.inventory_suggestions 生成库存管理建议表格
            inventory_suggestions = json.loads(forecast.inventory_suggestions) if forecast.inventory_suggestions else []
            data_inventory_daily = [["日期", "产品名称", "预测需求量", "当前库存量", "建议补货量"]]
            for sg in inventory_suggestions:
                data_inventory_daily.append([
                    sg['date'],  # 日期
                    forecast.category,  # 品类名称
                    sg['forecast_demand'],  # 预测需求量
                    sg['current_inventory'],  # 当前库存量
                    int(sg['reorder_quantity'])  # 建议补货量，确保为整数
                ])

            table_inventory_daily = Table(data_inventory_daily, repeatRows=1)  # 创建库存管理表格
            table_inventory_daily.setStyle(TableStyle([
                ('FONTNAME', (0, 0), (-1, 0), 'MSYH'),  # 设置字体样式
                ('BACKGROUND', (0, 0), (-1, 0), colors.grey),  # 表头背景色
                ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),  # 表头字体颜色
                ('ALIGN', (0, 0), (-1, -1), 'CENTER'),  # 内容居中对齐
                ('FONTNAME', (0, 1), (-1, -1), 'MSYH'),  # 内容字体样式
                ('FONTSIZE', (0, 0), (-1, -1), 10),  # 设置字体大小
                ('BOTTOMPADDING', (0, 0), (-1, 0), 12),  # 表头底部边距
                ('BACKGROUND', (0, 1), (-1, -1), colors.beige),  # 内容背景色
                ('GRID', (0, 0), (-1, -1), 1, colors.black),  # 设置表格网格线
            ]))
            elements.append(table_inventory_daily)  # 添加库存管理表格
            elements.append(Spacer(1, 12))  # 添加间隔

            # 生成库存管理建议图表（按月汇总）
            chart_inventory_filename = f"inventory_management_{uuid.uuid4().hex}.png"  # 创建唯一的文件名
            chart_inventory_path = os.path.join(os.path.dirname(filepath), chart_inventory_filename)
            self.create_inventory_management_chart(inventory_suggestions, chart_inventory_path, font_prop)  # 生成库存管理图表
            if os.path.exists(chart_inventory_path):
                im = Image(chart_inventory_path, width=6 * inch, height=4 * inch)
                elements.append(im)  # 添加图像到文档
                elements.append(Spacer(1, 12))  # 添加间隔

            # 构建 PDF
            doc.build(elements)

            # 删除图表图片文件
            if os.path.exists(chart_path):
                os.remove(chart_path)
            if os.path.exists(chart_inventory_path):
                os.remove(chart_inventory_path)

            current_app.logger.info(f"完整报告已保存至 {filepath}")  # 记录报告保存路径
        except Exception as e:
            current_app.logger.error(f"生成完整报告失败: {e}", exc_info=True)  # 记录错误日志
            raise e  # 抛出异常


    def create_inventory_management_chart(self, inventory_suggestions, chart_inventory_path, font_prop):
        try:
            # 按月汇总预测需求量
            monthly_forecast = defaultdict(int)  # 使用整数
            for sg in inventory_suggestions:
                month = sg['date'][:7]  # 提取 'YYYY-MM' 格式的月份
                monthly_forecast[month] += sg['forecast_demand']  # 累加每月的预测需求量

            sorted_months = sorted(monthly_forecast.keys())  # 按月份排序
            sorted_forecasts = [monthly_forecast[month] for month in sorted_months]  # 获取排序后的预测量

            # 计算每月建议补货量（基于预测需求量的80%）
            monthly_replenishment_sum = defaultdict(int)
            for sg in inventory_suggestions:
                month = sg['date'][:7]  # 按月份汇总
                replenishment = int(round(sg['forecast_demand'] * 0.8))  # 建议补货量，确保为整数
                monthly_replenishment_sum[month] += replenishment

            sorted_replenishments = [monthly_replenishment_sum[month] for month in sorted_months]  # 获取排序后的建议补货量

            x = range(len(sorted_months))  # x坐标

            plt.figure(figsize=(10, 6))  # 创建图表
            plt.bar(x, sorted_forecasts, width=0.4, label='预测需求量', color='skyblue', align='center')  # 绘制预测需求量柱状图
            plt.bar(x, sorted_replenishments, width=0.4, bottom=sorted_forecasts, label='建议补货量', color='salmon', align='center')  # 绘制建议补货量柱状图
            plt.xlabel('月份', fontproperties=font_prop)  # x轴标签
            plt.ylabel('数量', fontproperties=font_prop)  # y轴标签
            plt.title('库存管理建议柱状图', fontproperties=font_prop)  # 图表标题
            plt.xticks(x, sorted_months, rotation=45, fontproperties=font_prop)  # x轴刻度
            plt.legend(prop={'family': font_prop.get_name()})  # 图例
            plt.tight_layout()  # 自适应布局

            plt.savefig(chart_inventory_path)  # 保存图表
            plt.close()  # 关闭当前图表
            current_app.logger.info(f"库存管理建议图已保存: {chart_inventory_path}")  # 记录图表保存路径
        except Exception as e:
            current_app.logger.error(f"创建库存管理建议图失败: {e}", exc_info=True)  # 记录错误日志
            raise e  # 抛出异常

    def create_demand_trend_chart(self, demand_forecasts, chart_path, font_prop):
        try:
            # 按月汇总预测需求量
            monthly_forecast = defaultdict(int)  # 使用整数
            for df in demand_forecasts:
                month = df.forecast_date.strftime('%Y-%m')  # 提取 'YYYY-MM' 格式的月份
                monthly_forecast[month] += df.forecast_quantity  # 累加每月的预测需求量

            # 按时间排序
            sorted_months = sorted(monthly_forecast.keys())  # 排序
            sorted_forecasts = [monthly_forecast[month] for month in sorted_months]  # 获取排序后的预测量

            plt.figure(figsize=(10, 6))  # 创建图表
            plt.bar(sorted_months, sorted_forecasts, color='skyblue', label='预测需求量')  # 绘制预测需求量柱状图
            plt.xlabel('月份', fontproperties=font_prop)  # x轴标签
            plt.ylabel('预测需求量', fontproperties=font_prop)  # y轴标签
            plt.title('需求预测趋势图', fontproperties=font_prop)  # 图表标题
            plt.xticks(rotation=45, fontproperties=font_prop)  # x轴刻度
            plt.legend(prop={'family': font_prop.get_name()})  # 图例
            plt.tight_layout()  # 自适应布局

            plt.savefig(chart_path)  # 保存图表
            plt.close()  # 关闭当前图表
            current_app.logger.info(f"需求趋势图已保存: {chart_path}")  # 记录图表保存路径
        except Exception as e:
            current_app.logger.error(f"创建需求趋势图失败: {e}", exc_info=True)  # 记录错误日志
            raise e  # 抛出异常


class DownloadReport(Resource):
    @jwt_required()  # JWT 认证
    def get(self, report_id):
        # 获取当前用户
        current_user = get_current_user()
        if not current_user or current_user.role not in ['管理员', '普通用户']:
            # 若用户未授权，则返回403状态
            return {"message": "未授权访问"}, 403

        try:
            # 根据报告ID获取报告
            report = Report.query.get(report_id)
            if not report:
                return {"message": "报告不存在"}, 404  # 返回404状态

            # 权限控制
            if current_user.role != '管理员' and report.uploaded_file.user_id != current_user.id:
                return {"message": "未授权下载其他用户的报告"}, 403  # 返回403状态

            if not os.path.exists(report.filepath):
                return {"message": "报告文件不存在"}, 404  # 返回404状态

            return send_file(report.filepath, as_attachment=True)  # 下载文件
        except Exception as e:
            current_app.logger.error(f"下载报告失败: {e}")  # 记录错误日志
            return {"message": "下载报告失败，请重试。"}, 500  # 返回500状态


class DeleteReport(Resource):
    @jwt_required()  # JWT 认证
    def delete(self, report_id):
        # 获取当前用户
        current_user = get_current_user()
        if not current_user:
            return {"message": "用户身份验证失败"}, 401  # 返回401状态

        try:
            # 获取报告
            report = Report.query.get(report_id)
            if not report:
                return {"message": "报告不存在"}, 404  # 返回404状态

            # 获取报告的上传用户
            target_user = report.uploaded_file.user if report.uploaded_file else None

            if not target_user:
                return {"message": "关联的用户不存在"}, 404  # 返回404状态

            # 权限控制
            if current_user.role == '管理员':
                if target_user.role == '管理员' and not current_user.is_pzy():
                    return {"message": "您无权删除其他管理员的报告"}, 403  # 返回403状态
                # 管理员可以删除普通用户或pzy管理员的数据
            elif target_user.id == current_user.id:
                # 普通用户可以删除自己的报告
                pass
            else:
                return {"message": "未授权删除该报告"}, 403  # 返回403状态

            # 删除报告文件
            if os.path.exists(report.filepath):
                os.remove(report.filepath)  # 删除文件
                current_app.logger.info(f"已删除报告文件: {report.filepath}")  # 记录删除日志
            else:
                current_app.logger.warning(f"报告文件不存在: {report.filepath}")  # 记录警告日志

            # 从数据库中删除记录
            db.session.delete(report)  # 删除报告记录
            db.session.commit()  # 提交数据库修改

            return {"message": "报告删除成功"}, 200  # 返回成功状态
        except Exception as e:
            db.session.rollback()  # 回滚数据库修改
            current_app.logger.error(f"删除报告失败: {e}", exc_info=True)  # 记录错误日志
            return {"message": "报告删除失败，请重试。"}, 500  # 返回500状态


class GetForecastDataByDate(Resource):
    @jwt_required()  # JWT 认证
    def get(self):
        # 获取当前用户
        current_user = get_current_user()
        if not current_user or current_user.role not in ['管理员', '普通用户']:
            # 若用户未授权，则返回403状态
            return {"message": "未授权访问"}, 403

        forecast_id = request.args.get('forecast_id', type=int)  # 从查询参数中获取预测ID
        selected_date = request.args.get('date', type=str)  # 从查询参数中获取日期，应为 'YYYY-MM-DD'

        if not forecast_id or not selected_date:
            return {"message": "预测ID和日期是必需的"}, 400  # 返回400状态

        try:
            # 验证预测记录存在
            forecast = Forecast.query.get(forecast_id)
            if not forecast:
                return {"message": "预测记录不存在"}, 404  # 返回404状态

            # 权限控制
            if current_user.role != '管理员' and forecast.uploaded_file.user_id != current_user.id:
                return {"message": "未授权访问该预测数据"}, 403  # 返回403状态

            # 验证日期格式
            try:
                date_obj = datetime.datetime.strptime(selected_date, '%Y-%m-%d').date()  # 转换为日期对象
            except ValueError:
                return {"message": "日期格式错误，应为 'YYYY-MM-DD'"}, 400  # 返回400状态

            # 获取对应日期的 DemandForecast
            demand_forecast = DemandForecast.query.filter_by(
                forecast_id=forecast_id,
                forecast_date=date_obj
            ).first()

            if not demand_forecast:
                return {"message": "该日期没有预测数据"}, 404  # 返回404状态

            # 获取对应的库存管理记录
            inventory = InventoryManagement.query.filter_by(
                uploaded_file_id=forecast.uploaded_file_id,
                product_name=forecast.category
            ).first()

            if not inventory:
                return {"message": "库存管理记录不存在"}, 404  # 返回404状态

            # 从 Forecast 的 inventory_suggestions 中提取 reorder_quantity
            reorder_quantity = None
            if forecast.inventory_suggestions:
                try:
                    suggestions = json.loads(forecast.inventory_suggestions)  # 解析JSON建议
                    for sg in suggestions:
                        if sg['date'] == selected_date:
                            reorder_quantity = sg.get('reorder_quantity')  # 获取补货数量
                            break
                except json.JSONDecodeError:
                    current_app.logger.error("无法解析 inventory_suggestions JSON")  # 记录错误日志
                    return {"message": "内部服务器错误"}, 500  # 返回500状态

            return {
                "forecast_date": selected_date,
                "forecast_quantity": demand_forecast.forecast_quantity,
                "current_stock": inventory.current_stock,
                "reorder_quantity": reorder_quantity
            }, 200  # 返回数据

        except Exception as e:
            current_app.logger.error(f"获取预测数据失败: {e}", exc_info=True)  # 记录错误日志
            return {"message": "获取预测数据失败，请重试。"}, 500  # 返回500状态


def get_current_user():
    """
    Helper function to retrieve the current user object from JWT.
    获取当前用户对象的帮助函数，从 JWT 中提取用户身份信息。
    """
    current_identity = get_jwt_identity()  # 获取 JWT 身份
    if not current_identity:
        return None  # 如果没有身份，则返回 None
    # 提取用户名
    if isinstance(current_identity, dict):
        username = current_identity.get('username')  # 获取用户名
        if not username:
            current_app.logger.error("JWT identity does not contain 'username'")  # 记录错误日志
            return None  # 如果没有用户名，则返回 None
    else:
        username = current_identity  # 如果身份是字符串，则直接赋值
    user = User.query.filter_by(username=username).first()  # 根据用户名查询用户
    return user  # 返回用户对象
